﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestByWire.Test.EmulatedTutorials
{
    [TestClass]
    public class Basics : TestTarget<SalaryManager>
    {
        [TestMethod]
        public void EverythingIsInitialized()
        {
            double newSalery = When.Calling.RaiseSalery("foo");
            Assert.AreEqual(0, newSalery);
        }

        [TestMethod]
        public void SetUpDependencyBehaviour()
        {
            Given.That<ITaxationCalulator>(x => x.CalculateTax(0)).Returns(200d);

            double salery = When.Calling.RaiseSalery("foo");

            Assert.AreEqual(200, salery);
        }

        [TestMethod]
        public void AssertOnDependencyBehaviour()
        {
            var employeeInfomation = new EmployeInformation();
            Given.That<IEmployeStorage>(x => x.RetrieveCurrentSalery("foo")).Returns(employeeInfomation);
            Given.That<ITaxationCalulator>(x => x.CalculateTax(0)).Returns(200d);

            When.Calling.RaiseSalery("foo");

            Then<IEmployeStorage>.Should(x => x.UpdateSalery(employeeInfomation, 200));

            // Pure BDD - readss like Gherkin
            // Given that IEmployeStorage.RetrieveCurrentSalery(0) returns employeeinformation (and)
            // Given that ITaxationCalculator.CalculateTax(0) returns 200
            // When calling SaleryManager.RaiseSalery("foo")
            // Then IEmployeStorage should UpdateSalery(employeeInformation, 200)
        }

        [TestMethod]
        public void IndirectRuntimeDependenciesAreConfigured()
        {
            Given.That<ISaleryStrategy>(x => x.CalculateSaleryAdjustment(0)).Returns(300d);

            double salery = When.Calling.RaiseSalery("foo");

            Assert.AreEqual(300, salery);
        }

        [TestMethod, ExpectedException(typeof(InvalidOperationException))]
        public void ThrowException()
        {
            Given.That<ISaleryStrategy>(x => x.CalculateSaleryAdjustment(0)).Throws(new InvalidOperationException());

            When.Calling.RaiseSalery("foo");
        }

        [TestMethod]
        public void RepeatAction()
        {
            Given.That<IEmployeStorage>(x => x.RetrieveCurrentSalery("foo")).Returns(new EmployeInformation{Salery=20}).Repeat(2);
			Given.That<IEmployeStorage>(x => x.RetrieveCurrentSalery("foo")).Returns(new EmployeInformation { Salery = 30 });

            double[] saleries = When.Calling.GetSaleries("foo", "foo", "foo");

            Assert.AreEqual(20, saleries[0]);
            Assert.AreEqual(20, saleries[1]);
            Assert.AreEqual(30, saleries[2]);
        }

        [TestMethod]
        public void RaiseEvent()
        {
            When<IEventRaiser>.RaiseEvent(x => x.eventToRaise += null);

            Then<IEmployeStorage>.Should(x => x.RetrieveCurrentSalery(null));
        }

        [TestMethod]
        public void RaiseEventWithArgument()
        {
            When<IEventRaiser>.RaiseEvent(x => x.eventToRaise += null, new StringEventArgs("bar"));

            Then<IEmployeStorage>.Should(x => x.RetrieveCurrentSalery("bar"));
        }

        [TestMethod]
        public void OnlyOneAssertionPerTest()
        {
            When.Calling.GetSaleries();

            Then<IEmployeStorage>.FulFills(x => true);
            try
            {
                Then<IEmployeStorage>.FulFills(x => true);
                Assert.Fail("Only one assertion per test.");
            }
            catch (InvalidOperationException)
            {
            }
        }
    }

    public class SalaryManager
    {
        private readonly IEmployeStorage _storage;
        private readonly ISaleryIndex _saleryIndex;
        private readonly ITaxationCalulator _taxCalculator;

        public SalaryManager(IEmployeStorage storage, ISaleryIndex saleryIndex, ITaxationCalulator taxCalculator, IEventRaiser eventRaiser)
        {
            _storage = storage;
            _saleryIndex = saleryIndex;
            _taxCalculator = taxCalculator;
            eventRaiser.eventToRaise += OnUpdate;
        }

        private void OnUpdate(object sender, StringEventArgs e)
        {
            RaiseSalery(e == null ? null : e.Name);
        }

        public double RaiseSalery(String employeeId)
        {
            EmployeInformation emplInfo = _storage.RetrieveCurrentSalery(employeeId);
            ISaleryStrategy salStrategy = _saleryIndex.GetSaleryStrategy(emplInfo.Title);
            double saleryAdjustment = salStrategy.CalculateSaleryAdjustment(emplInfo.Salery);
            double tax = _taxCalculator.CalculateTax(saleryAdjustment);
            double newSalery = emplInfo.Salery + saleryAdjustment + tax;
            _storage.UpdateSalery(emplInfo, newSalery);
            return newSalery;
        }

        public double[] GetSaleries(params String[] employes)
        {
            var outSal = new double[employes.Length];
            for (int i = 0; i < employes.Length; i++)
            {
                outSal[i] = _storage.RetrieveCurrentSalery(employes[i]).Salery;
            }

            return outSal;
        }
    }

    public class StringEventArgs : EventArgs
    {
        public string Name { get; set; }

        public StringEventArgs(String name)
        {
            Name = name;
        }
    }

    public interface IEventRaiser
    {
        event EventHandler<StringEventArgs> eventToRaise;
    }

    public struct EmployeInformation
    {
        public string Title { get; set; }

        public double Salery { get; set; }

        public bool Equals(EmployeInformation other)
        {
            return Equals(other.Title, Title) && other.Salery.Equals(Salery);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof(EmployeInformation)) return false;
            return Equals((EmployeInformation)obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return ((Title != null ? Title.GetHashCode() : 0) * 397) ^ Salery.GetHashCode();
            }
        }
    }

    public interface ITaxationCalulator
    {
        double CalculateTax(double saleryAdjustment);
    }

    public interface ISaleryIndex
    {
        ISaleryStrategy GetSaleryStrategy(string employeeId);
    }

    public interface ISaleryStrategy
    {
        double CalculateSaleryAdjustment(double currentSalery);
    }

    public interface IEmployeStorage
    {
        EmployeInformation RetrieveCurrentSalery(string employeeId);
        void UpdateSalery(EmployeInformation employeInformation, double newSalery);


    }
}
